package common;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

public class Utilities {
	public static byte[] serializeString(String s) throws IOException {		
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		DataOutputStream dos = new DataOutputStream(baos);
		
		dos.writeChars(s); // dos.write(s.getBytes());
		dos.close();
		
		byte[] serializedString = baos.toByteArray();
		
		return serializedString;
	}
	
	public static String deserializeString(byte[] b) throws IOException {
		String s = "";
		ByteArrayInputStream bais = new ByteArrayInputStream(b);
		DataInputStream dis = new DataInputStream(bais);
		
		char c = dis.readChar();
		while(c != '\0') {
			s += c;
			try {
				c = dis.readChar();
			}
			catch(EOFException e) {
				break;
			}
		}
		
		return s;		
	}
	
	public static void printUsage(boolean sender) {
		if(sender) {
			System.out.println("sendFile -r <recv_host>:<recv_port> -f <filename>");		
		}
		else {
			System.out.println("recvFile -p <recv_port>");			
		}
	}
	
	public static long computeCheckSum(DatagramPacket packet) {
		byte[] packetBuffer = packet.getData();
		return computeCheckSum(packetBuffer);
	}
		
	public static long computeCheckSum(byte[] packetBuffer) {
		// Compute Adler-32 checksum (faster computation than CRC, less reliable)
		/*Checksum checksumEngine = new Adler32();
		checksumEngine.update(packetArray, 0, packetArray.length);
		long adlerChecksum = checksumEngine.getValue();*/
		
		// Compute CRC-32 checksum
		Checksum checksumEngine = new CRC32();
		checksumEngine.update(packetBuffer, 0, packetBuffer.length);
		long crcChecksum = checksumEngine.getValue();
				    
		return crcChecksum;
	}
	
	public static void main(String[] args) throws IOException {
		InetAddress address = InetAddress.getByName("127.0.0.1");
	    
		// first packet sent is START
		byte[] data = Utilities.serializeString("steps.txt");
		
		Header startHeader = new Header(1, PacketType.PACKET_START, data.length);
	    // serialize packet header
		byte[] serializedStartHeader = startHeader.serialize();
					
		byte[] serializedStartPacket = new byte[serializedStartHeader.length + data.length];
		System.arraycopy(serializedStartHeader, 0, serializedStartPacket, 0, serializedStartHeader.length);
		System.arraycopy(data, 0, serializedStartPacket, serializedStartHeader.length, data.length);

		// construct the packet
		DatagramPacket startPacket = new DatagramPacket(serializedStartPacket, 
														serializedStartPacket.length,
														address,
														9000);
		
		long cs1 = computeCheckSum(startPacket);
		long cs2 = computeCheckSum(startPacket);
		System.out.println(cs1 == cs2);
	}
	
	public static boolean correctChecksum(DatagramPacket packet) throws IOException {
		  Header header = extractHeader(packet);
		  long validCheckSum = header.getCheckSum();

		  header.setCheckSum(0);
		  byte[] serializedHeader = header.serialize();	  
		  byte[] data = extractData(packet);

		  byte[] serializedPacket = new byte[Header.HEADER_SIZE + header.getLength()];	  
		  System.arraycopy(serializedHeader, 0, serializedPacket, 0, serializedHeader.length);
		  System.arraycopy(data, 0, serializedPacket, serializedHeader.length, data.length);

		  long recvCheckSum = Utilities.computeCheckSum(serializedPacket);
		
		  return validCheckSum == recvCheckSum;
		  //return true;
	  }
	  
	  /**
	   * Extract the Header from the Packet.
	   * @param p
	   * @return the header from the beginning of the packet.
	   * @throws IOException
	   */
	  public static Header extractHeader(DatagramPacket p) throws IOException {
	    // Get the header.
	    byte[] serializedHeader = new byte[Header.HEADER_SIZE];
	    System.arraycopy(p.getData(), 0, serializedHeader, 0, Header.HEADER_SIZE);
	    Header header = Header.deserialize(serializedHeader);
	    return header;
	  }
	  
	  /**
	   * Extract Data.
	   * @param packet
	   * @param header
	   * @return the data contained in the packet.
	   * @throws IOException
	   */
	  public static byte[] extractData(DatagramPacket packet) throws IOException {
	    Header header = extractHeader(packet);
	    int dataLength = header.getLength();
	    // Create a new byte orray of the right length.
	    byte[] data = new byte[dataLength];
	    // Copy the right section in.
	    System.arraycopy(packet.getData(), Header.HEADER_SIZE, data, 0, dataLength);
	    return data;
	  }
}
